/*
 * Copyright 2018 Google Inc.
 *
 * Use of this source code is governed by a BSD-style license that can be
 * found in the LICENSE file.
 */

#ifndef QuadPerEdgeAA_DEFINED
#define QuadPerEdgeAA_DEFINED

#include "include/core/SkPoint.h"
#include "include/core/SkPoint3.h"
#include "include/private/gpu/ganesh/GrTypesPriv.h"
#include "src/gpu/BufferWriter.h"
#include "src/gpu/ganesh/GrBuffer.h"
#include "src/gpu/ganesh/GrColor.h"
#include "src/gpu/ganesh/GrGeometryProcessor.h"
#include "src/gpu/ganesh/GrSamplerState.h"
#include "src/gpu/ganesh/geometry/GrQuad.h"
#include "src/gpu/ganesh/geometry/GrQuadUtils.h"
#include "src/gpu/ganesh/ops/TextureOp.h"

class GrCaps;
class GrColorSpaceXform;
class GrMeshDrawTarget;
struct GrShaderCaps;
struct VertexWriter;

namespace skgpu::ganesh::QuadPerEdgeAA {
using Saturate = skgpu::ganesh::TextureOp::Saturate;

enum class CoverageMode { kNone, kWithPosition, kWithColor };
enum class Subset : bool { kNo = false, kYes = true };
enum class ColorType { kNone, kByte, kFloat, kLast = kFloat };
static const int kColorTypeCount = static_cast<int>(ColorType::kLast) + 1;

enum class IndexBufferOption {
    kPictureFramed, // geometrically AA'd   -> 8 verts/quad + an index buffer
    kIndexedRects,  // non-AA'd but indexed -> 4 verts/quad + an index buffer
    kTriStrips,     // non-AA'd             -> 4 verts/quad but no index buffer
    kLast = kTriStrips
};
static const int kIndexBufferOptionCount = static_cast<int>(IndexBufferOption::kLast) + 1;

IndexBufferOption CalcIndexBufferOption(GrAAType aa, int numQuads);

// Gets the minimum ColorType that can represent a color.
ColorType MinColorType(SkPMColor4f);

// Specifies the vertex configuration for an op that renders per-edge AA quads. The vertex
// order (when enabled) is device position, color, local position, subset, aa edge equations.
// This order matches the constructor argument order of VertexSpec and is the order that
// GPAttributes maintains. If hasLocalCoords is false, then the local quad type can be ignored.
struct VertexSpec {
public:
    VertexSpec()
        : fDeviceQuadType(0) // kAxisAligned
          ,
          fLocalQuadType(0) // kAxisAligned
          ,
          fIndexBufferOption(0) // kPictureFramed
          ,
          fHasLocalCoords(false),
          fColorType(0) // kNone
          ,
          fHasSubset(false),
          fUsesCoverageAA(false),
          fCompatibleWithCoverageAsAlpha(false),
          fRequiresGeometrySubset(false)
    {}

    VertexSpec(GrQuad::Type deviceQuadType, ColorType colorType, GrQuad::Type localQuadType, bool hasLocalCoords,
        Subset subset, GrAAType aa, bool coverageAsAlpha, IndexBufferOption indexBufferOption)
        : fDeviceQuadType(static_cast<unsigned>(deviceQuadType)),
          fLocalQuadType(static_cast<unsigned>(localQuadType)),
          fIndexBufferOption(static_cast<unsigned>(indexBufferOption)),
          fHasLocalCoords(hasLocalCoords),
          fColorType(static_cast<unsigned>(colorType)),
          fHasSubset(static_cast<unsigned>(subset)),
          fUsesCoverageAA(aa == GrAAType::kCoverage),
          fCompatibleWithCoverageAsAlpha(coverageAsAlpha),
          fRequiresGeometrySubset(aa == GrAAType::kCoverage && deviceQuadType > GrQuad::Type::kRectilinear)
    {}

    GrQuad::Type deviceQuadType() const
    {
        return static_cast<GrQuad::Type>(fDeviceQuadType);
    }
    GrQuad::Type localQuadType() const
    {
        return static_cast<GrQuad::Type>(fLocalQuadType);
    }
    IndexBufferOption indexBufferOption() const
    {
        return static_cast<IndexBufferOption>(fIndexBufferOption);
    }
    bool hasLocalCoords() const
    {
        return fHasLocalCoords;
    }
    ColorType colorType() const
    {
        return static_cast<ColorType>(fColorType);
    }
    bool hasVertexColors() const
    {
        return ColorType::kNone != this->colorType();
    }
    bool hasSubset() const
    {
        return fHasSubset;
    }
    bool usesCoverageAA() const
    {
        return fUsesCoverageAA;
    }
    bool compatibleWithCoverageAsAlpha() const
    {
        return fCompatibleWithCoverageAsAlpha;
    }
    bool requiresGeometrySubset() const
    {
        return fRequiresGeometrySubset;
    }
    // Will always be 2 or 3
    int deviceDimensionality() const;
    // Will always be 0 if hasLocalCoords is false, otherwise will be 2 or 3
    int localDimensionality() const;

    int verticesPerQuad() const
    {
        return fUsesCoverageAA ? 8 : 4;
    }

    CoverageMode coverageMode() const;
    size_t vertexSize() const;

    bool needsIndexBuffer() const
    {
        return this->indexBufferOption() != IndexBufferOption::kTriStrips;
    }

    GrPrimitiveType primitiveType() const
    {
        switch (this->indexBufferOption()) {
            case IndexBufferOption::kPictureFramed:
                return GrPrimitiveType::kTriangles;
            case IndexBufferOption::kIndexedRects:
                return GrPrimitiveType::kTriangles;
            case IndexBufferOption::kTriStrips:
                return GrPrimitiveType::kTriangleStrip;
        }

        SkUNREACHABLE;
    }

private:
    static_assert(GrQuad::kTypeCount <= 4, "GrQuad::Type doesn't fit in 2 bits");
    static_assert(kColorTypeCount <= 4, "Color doesn't fit in 2 bits");
    static_assert(kIndexBufferOptionCount <= 4, "IndexBufferOption doesn't fit in 2 bits");

    unsigned fDeviceQuadType : 2;
    unsigned fLocalQuadType : 2;
    unsigned fIndexBufferOption : 2;
    unsigned fHasLocalCoords : 1;
    unsigned fColorType : 2;
    unsigned fHasSubset : 1;
    unsigned fUsesCoverageAA : 1;
    unsigned fCompatibleWithCoverageAsAlpha : 1;
    // The geometry subset serves to clip off pixels touched by quads with sharp corners that
    // would otherwise exceed the miter limit for the AA-outset geometry.
    unsigned fRequiresGeometrySubset : 1;
};

// A Tessellator is responsible for processing a series of device+local GrQuads into a VBO,
// as specified by a VertexSpec. This vertex data can then be processed by a GP created with
// MakeProcessor and/or MakeTexturedProcessor.
class Tessellator {
public:
    explicit Tessellator(const VertexSpec &spec, char *vertices);

    // Calculates (as needed) inset and outset geometry for anti-aliasing, and appends all
    // necessary position and vertex attributes required by this Tessellator's VertexSpec into
    // the 'vertices' the Tessellator was called with. The insetting and outsetting may
    // damage the provided GrQuads (as this is intended to work with GrQuadBuffer::Iter).
    // 'localQuad' can be null if the VertexSpec does not use local coords.
    void append(GrQuad *deviceQuad, GrQuad *localQuad, const SkPMColor4f &color, const SkRect &uvSubset,
        GrQuadAAFlags aaFlags);

    SkDEBUGCODE(skgpu::BufferWriter::Mark vertexMark() const { return fVertexWriter.mark(); })

        private :
        // VertexSpec defines many unique ways to write vertex attributes, which can be handled
        // generically by branching per-quad based on the VertexSpec. However, there are several
        // specs that appear in the wild far more frequently, so they use explicit WriteQuadProcs
        // that have no branches.
        typedef void (*WriteQuadProc)(VertexWriter *vertices, const VertexSpec &spec, const GrQuad *deviceQuad,
        const GrQuad *localQuad, const float coverage[4], const SkPMColor4f &color, const SkRect &geomSubset,
        const SkRect &texSubset);
    static WriteQuadProc GetWriteQuadProc(const VertexSpec &spec);

    GrQuadUtils::TessellationHelper fAAHelper;
    VertexSpec fVertexSpec;
    VertexWriter fVertexWriter;
    WriteQuadProc fWriteProc;
};

GrGeometryProcessor *MakeProcessor(SkArenaAlloc *, const VertexSpec &);

GrGeometryProcessor *MakeTexturedProcessor(SkArenaAlloc *, const VertexSpec &, const GrShaderCaps &,
    const GrBackendFormat &, GrSamplerState, const skgpu::Swizzle &, sk_sp<GrColorSpaceXform> textureColorSpaceXform,
    Saturate);

// This method will return the correct index buffer for the specified indexBufferOption.
// It will, correctly, return nullptr if the indexBufferOption is kTriStrips.
sk_sp<const GrBuffer> GetIndexBuffer(GrMeshDrawTarget *, IndexBufferOption);

// What is the maximum number of quads allowed for the specified indexBuffer option?
int QuadLimit(IndexBufferOption);

// This method will issue the draw call on the provided GrOpsRenderPass, as specified by the
// indexing method in vertexSpec. It is up to the calling code to allocate, fill in, and bind a
// vertex buffer, and to acquire and bind the correct index buffer (if needed) with
// GrPrimitiveRestart::kNo.
//
// @param runningQuadCount  the number of quads already stored in 'vertexBuffer' and
//                          'indexBuffer' e.g., different GrMeshes have already been placed in
//                          the buffers to allow dynamic state changes.
// @param quadCount         the number of quads that will be drawn by the provided 'mesh'.
//                          A subsequent ConfigureMesh call would the use
//                          'runningQuadCount' + 'quadCount' for its new 'runningQuadCount'.
void IssueDraw(const GrCaps &, GrOpsRenderPass *, const VertexSpec &, int runningQuadCount, int quadCount, int maxVerts,
    int absVertBufferOffset);
} // namespace skgpu::ganesh::QuadPerEdgeAA

#endif // QuadPerEdgeAA_DEFINED
